﻿using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Specialized;

using System.ComponentModel;
using System.Drawing;
using System.Web.SessionState;
using System.Web.UI.DataVisualization.Charting;
using SCRUM.Core.DAO;
using SCRUM.Core.BusinessObjects;
using System.Collections.Generic;
using System.Data.Common;
using SCRUM.Core.DataProviders;

/// <summary>
/// Widget to display burndown chart for sprints
/// </summary>
public partial class BurndownChart : WidgetBase
{
    #region [ BASE ]

    /// <summary>
    /// Gets the name of the widget. It must be exactly the same as the folder that contains the widget.
    /// </summary>
    /// <value></value>
    public override string Name
    {
        get { return "BurndownChart"; }
    }

    /// <summary>
    /// Gets the title. Title is shown in the header of the widget container.
    /// </summary>
    /// <value>The title.</value>
    public override string Title
    {
        get { return "StoryPoints Chart"; }
    }

    #endregion

    
    /// <summary>
    /// Settings for Burndownchart
    /// </summary>
    private StringDictionary settings;       

    /// <summary>
    /// This method works as a substitute for Page_Load. You should use this method for
    /// data binding etc. instead of Page_Load.
    /// </summary>
    public override void LoadWidget()
    {
        SCRUM.Core.BusinessObjects.UserSettings us = DAOUserSettings.GetSettingsForLoggedUser();
        SetChartColour(us.Theme);
        loadAvailableOptions();
        SetSettings();

        Chart1.ChartAreas["ChartArea1"].Visible = false;
        Chart1.Titles[0].Text = (string)GetLocalResourceObject("NoSprintSelected");
    }

    private void SetChartColour(string theme)
    {
        switch(theme)
        {
            case "Green": Chart1.BackColor = System.Drawing.ColorTranslator.FromHtml("#90EE90"); break;
            default: Chart1.BackColor = System.Drawing.ColorTranslator.FromHtml("#D3DFF0"); break; 
        }
            
    }

    /// <summary>
    /// Gets a value indicating whether this instance is editable.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if this instance is editable; otherwise, <c>false</c>.
    /// </value>
    public override bool IsEditable
    {
        get { return true; }
    }

    /// <summary>
    /// Shows the edit.
    /// </summary>
    public override void ShowEdit()
    {
        base.ShowEdit();
        EditBurndownChart.Visible = true;
    }

    /// <summary>
    /// Hides the edit.
    /// </summary>
    public override void HideEdit()
    {
        base.HideEdit();
        EditBurndownChart.Visible = false;
    }

    /// <summary>
    /// List of available options
    /// </summary>
    private string[] options = { "3D" };

    /// <summary>
    /// Loads all the types of chart to display
    /// </summary>
    private void loadAvailableOptions()
    {
        foreach (string option in options)
        {
            CheckBox checkBox = new CheckBox();
            checkBox.Text = (string)GetLocalResourceObject(option) + " ";
            checkBox.Checked = false;
            this.AvailableOptions.Controls.Add(checkBox);
        }
    }

    /// <summary>
    /// Gets the default settings for this widget
    /// </summary>
    /// <returns>settings for widget</returns>
    public override StringDictionary GetDefaultSettings()
    {
        StringDictionary defaultSettings = new StringDictionary();

        foreach (string option in options)
        {
            defaultSettings.Add(option + "checked", "false");
        }
        return defaultSettings;
    }

    /// <summary>
    /// Sets the settings.
    /// </summary>
    protected void SetSettings()
    {
        this.settings = GetSettings();

        for (int i = 0; i < options.Length; i++)
        {
            string option = options[i].ToLower();
            bool isTrue = this.settings[option + "checked"].ToLower() == "true";
            (this.AvailableOptions.Controls[i] as CheckBox).Checked = isTrue;
            useSettings(i, isTrue);
        }

    }

    /// <summary>
    /// Applies settings to widget
    /// </summary>
    /// <param name="option">All options</param>
    /// <param name="chosen">Choosen option</param>
    private void useSettings(int option, bool chosen)
    {
        switch (option)
        {
            case 0:
                {
                    if (chosen)
                    {
                        Chart1.ChartAreas["ChartArea1"].Area3DStyle.Enable3D = true;
                    }
                    else
                    {
                        Chart1.ChartAreas["ChartArea1"].Area3DStyle.Enable3D = false;
                    }
                    break;
                }
            default: break;
        }
    }

    /// <summary>
    /// Occurs when user clicks on Save Settings 
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void SaveSettingsClick(object sender, EventArgs e)
    {

        int i = 0;
        foreach (string option in options)
        {
            settings[option + "checked"] = (AvailableOptions.Controls[i++] as CheckBox).Checked.ToString();
        }

        base.SaveSettings(settings);
        this.HideEdit();
        //LoadWidget();
        SetSettings();
        LoadBurndownchart(this, new EventArgs());
    }


    /// <summary>
    /// Handler to manage choosen option of type of chart
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void ChooseChartTypeOptions(object sender, System.EventArgs e)
    {
        if (ChartTypes.SelectedChart != null)
        {
            switch (ChartTypes.SelectedChart)
            {
                case "Burndownchart for sprint": SprintBurndown.Visible = true; break;
                case "user": hideOptions(); break;
                default: break;
            }
        }
    }

    /// <summary>
    /// Loads the burndownchart.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void LoadBurndownchart(object sender, EventArgs e)
    {
        
        if (SprintBurndown.SelectedSprint == null)
        {
            return;
        }

        Chart1.ChartAreas["ChartArea1"].Visible = true;
        Sprint s = SprintBurndown.SelectedSprint;
        //DateTime today = new DateTime();
        //today = DateTime.Today;

        //DateTime dt = s.FromDate.Date;

        Chart1.Titles[0].Text = s.Name;
        Chart1.ChartAreas[0].AxisY.Title = (string)GetLocalResourceObject("StoryPoints");
        Chart1.ChartAreas[0].AxisX.Title = (string)GetLocalResourceObject("Days");
        //DAOBurndownchart.GetChartSource(Chart1, SprintBurndown.SelectedSprint.ID);
        IList<DailyWorkProgress> dailyprogress = DAOBurndownchart.GetProgressForSprint(s.ID);

        setChartSource(Chart1, s, dailyprogress);


        IList<State> states = DAOState.GetAllStatesForType(State.StateTypes.Task);

        //Chart1.Legends["Default"].Docking = Docking.Right;
        //LegendItem li = new LegendItem();
        //li.Name = states[0].Name;
        //li.Color = System.Drawing.Color.Red;
        //Chart1.Legends["Default"].CustomItems.Add(li)
        for (int i = 0; i < states.Count; i++)
        {
            Chart1.Series[i].Color = chooseColor(i);
            //LegendItem li = new LegendItem();
            //li.Name = states[i].Name;
            //li.Color = chooseColor(i);
            ////Chart1.Legends["Default"].CustomItems.Add(li);
            ////Legend l = new Legend(states[i].Name);
            ////Chart1.Legends.Add(l);
            ////Chart1.Legends[states[i].Name].Docking = Docking.Right;
            ////Chart1.Series[states[i].Name].Legend = states[i].Name;
            ////Chart1.Series[states[i].Name].IsVisibleInLegend = true;// = states[i].Name;
        }


        setOptimalSpline(Chart1, s);
        

    }

    /// <summary>
    /// Creates optimal line for sprint and place it to chart
    /// </summary>
    /// <param name="Chart1">The chart to create line for</param>
    /// <param name="s">Sprint to display in chart</param>
    private void setOptimalSpline(Chart Chart1, Sprint s)
    {

        Series series = new Series("Optimum");
        series.ChartType = SeriesChartType.Spline;
        series.BorderWidth = 3;
        series.ShadowOffset = 2;

        //series.Points.AddY(s.Estimation);

        int days = (s.ToDate - s.FromDate).Days;
        double daywork = s.Estimation / days;

        for (int i = 0; i < days; i++)
        {
            series.Points.AddY(s.Estimation - i * daywork);
        }
        series.Points.AddY(0);

        Chart1.Series.Add(series);
    }

    /// <summary>
    /// SEts source data for chart
    /// </summary>
    /// <param name="ch">The chart</param>
    /// <param name="s">The sprint to display in chart</param>
    /// <param name="dailyprogress">List of daily progresses</param>
    private void setChartSource(Chart ch, Sprint s, IList<DailyWorkProgress> dailyprogress)
    {
        IList<Task> tasks = DAOTask.GetAllTasks(-1, -1, s.ID, -1, -1, -1);
        

        IList<State> states = DAOState.GetAllStatesForType(State.StateTypes.Task);
        int statesCount = states.Count;
        DateTime dt = s.FromDate;

        for (int j = 0; j < statesCount; j++)
        {
            ch.Series.Add(states[j].Name);
            //ch.Series[j.ToString()].Color = chooseColor(j);
            ch.Series[states[j].Name].ChartType = SeriesChartType.StackedColumn;
        }
        //double[] stateEstimation = new double[statesCount];
        double proposedEstimation = 0;
        for (int i = 0; i < tasks.Count; i++)
        {
            if (tasks[i].StateID == 1)
            {
                proposedEstimation += tasks[i].Estimation;
            }
        }
        double[] previousDay = new double[statesCount];
        previousDay[0] = s.Estimation;
        for (int i = 1; i < statesCount; i++)
        {
            previousDay[i] = 0;
        }

        //for (int i = 0; i < dailyprogress.Count; i++)
        int record = 0;
        //check all records in workprogress for sprint
        while(record < dailyprogress.Count)
        {
            DateTime d = dailyprogress[0].Date;

            //if there is no record for some day(s), we use data from previous day
            while ( dt.CompareTo(d) == -1 /*|| isWorkingDay(dt.DayOfWeek)*/)
            {
                for (int j = 0; j < statesCount; j++)
                {
                    ch.Series[states[j].Name].Points.AddY(previousDay[j]);
                }
                dt = dt.AddDays(1);
            }

            bool more = readSameDay(ch, dailyprogress, ref record, ref previousDay, states, proposedEstimation);
            dt = dt.AddDays(1);

            //for the rest (future) days .. zeroes
            if (more == false)
            {
                while (dt.CompareTo(s.ToDate) <= 0)
                {
                    for (int j = 0; j < statesCount; j++)
                    {
                        ch.Series[states[j].Name].Points.AddY(0);//DAOSprint.GetValueForTaskInState(s.ID, states[j].Id));
                    }
                    dt = dt.AddDays(1);
                }
            }
        }
    }

    /// <summary>
    /// Method to read data for one day to display in chart
    /// </summary>
    /// <param name="ch">The chart</param>
    /// <param name="dailyprogress">List of daily progresses</param>
    /// <param name="record">number of records</param>
    /// <param name="prev">Previous day progresses</param>
    /// <param name="states">States</param>
    /// <param name="proposedEstimation">Proposed estimation</param>
    /// <returns></returns>
    private bool readSameDay(Chart ch, IList<DailyWorkProgress> dailyprogress, ref int record, ref double[] prev, IList<State> states, double proposedEstimation)
    {
        bool more = true;
        double work = 0;
        int j = 1;
        
        while (j < prev.Length)
        {
            while (j < prev.Length && states[j].ID != dailyprogress[record].StateID)
            {
                ch.Series[states[j].Name].Points.AddY(prev[j]);
                ++j;
            }
            if (j < prev.Length)
            {
                prev[j] += dailyprogress[record].StoryPoints;
                work += dailyprogress[record].StoryPoints;
                //prev[0] -= work;
                ch.Series[states[j].Name].Points.AddY(prev[j]);
                ++j;
                DateTime dt = dailyprogress[record].Date;
                ++record;
                if (record>=dailyprogress.Count)
                {
                    while (j < prev.Length)
                    {
                        ch.Series[states[j].Name].Points.AddY(prev[j]);
                        ++j;
                    }
                    break;
                }

                if (dt.CompareTo(/*(DateTime)data[0]*/ dailyprogress[record].Date) != 0)
                {
                    while (j < prev.Length)//&& states[j].Id != states[states.Count - 1].Id)
                    {
                        ch.Series[states[j].Name].Points.AddY(prev[j]);
                        ++j;
                    }
                }
            }
            else
            {
                more = ++record < dailyprogress.Count ? true : false;                
            }
        }
        double max = Math.Max(prev[0] - proposedEstimation, prev[0] - work);
        prev[0] = max > 0 ? max : 0;
        ch.Series[states[0].Name].Points.AddY(prev[0]);
        return more;
    }

    
    /// <summary>
    /// Determines whether day is working day.
    /// </summary>
    /// <param name="dw">The day of the week.</param>
    /// <returns>
    /// 	<c>true</c> if day is working day; otherwise, <c>false</c>.
    /// </returns>
    private bool isWorkingDay(DayOfWeek dw)
    {
        if (dw != DayOfWeek.Saturday && dw != DayOfWeek.Sunday)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Hides the options
    /// </summary>
    private void hideOptions()
    {
        SprintBurndown.Visible = false;
    }
    

    /// <summary>
    /// Color resolver
    /// </summary>
    /// <param name="j">id of color</param>
    /// <returns>Color object</returns>
    private Color chooseColor(int j)
    {
        switch (j)
        {
            case 0: return Color.Red;
            case 1: return Color.Orange;
            case 2: return Color.Yellow;
            case 3: return Color.YellowGreen;
            case 4: return Color.GreenYellow;
            default: return Color.Green;
        }
    }

    
}